namespace Angelus.Data.Linq.ProviderBase
{
    using System.Expressions;
    using System.Collections.Generic;
    using System.Data.DLinq.ProviderBase;

    internal class OracleMultiplexer
    {
        // Methods
        private readonly Visitor visitor;

        internal OracleMultiplexer(SqlBinder binder, TypeSystemProvider typeProvider)
        {
            this.visitor = new Visitor(binder, typeProvider);
        }

        internal SqlNode Multiplex(SqlNode node)
        {
            return this.visitor.Visit(node);
        }

        // Fields

        // Nested Types

        #region Nested type: Visitor

        private class Visitor : SqlVisitor
        {
            // Methods
            private readonly SqlBinder binder;
            private readonly OracleFactory sql;
            private Scope currentScope;
            private bool isTopLevel;
            private SqlQuerySet querySet;

            internal Visitor(SqlBinder binder, TypeSystemProvider typeProvider)
            {
                this.binder = binder;
                this.currentScope = new Scope(null, null, null);
                this.sql = new OracleFactory(typeProvider);
            }

            private SqlExpression ConvertToClientQuery(SqlSubSelect ss)
            {
                SqlDuplicator duplicator2 = new SqlDuplicator(false);
                SqlSelect select4 =
                    this.InvertSelect(new Scope(this.currentScope, this.currentScope.DownstreamScope, ss.Select),
                                      duplicator2, true, ss.SourceExpression);
                select4.OrderingType = SqlOrderingType.Always;
                select4 = (SqlSelect) this.binder.Bind(select4);
                int num2 = this.querySet.Queries.Count;
                this.querySet.Queries.Add(select4);
                SqlAlias alias2 = new SqlAlias(Visit(ss.Select));
                SqlSelect select5 = new SqlSelect(this.sql.Unary(SqlNodeType.Count, null, ss.SourceExpression), alias2,
                                                  ss.SourceExpression);
                SqlSubSelect select6 = this.sql.SubSelect(SqlNodeType.ScalarSubSelect, select5);
                SqlNodeType type2 = (ss.NodeType == SqlNodeType.Multiset)
                                        ? SqlNodeType.ClientCollection
                                        : SqlNodeType.ClientElement;
                SqlClientQuery query2 = new SqlClientQuery(type2, ss.ClrType, ss.SqlType, select6, ss.SourceExpression);
                query2.Query = ss.Select;
                query2.QuerySet = this.querySet;
                query2.Index = num2;
                if (this.currentScope.Select != null)
                {
                    this.currentScope.Select.OrderingType = SqlOrderingType.Always;
                }
                return query2;
            }

            private static Scope GetFurthestDownstreamScope(Scope scope)
            {
                while ((scope.DownstreamScope != null) && (scope.DownstreamScope.Select != null))
                {
                    scope = scope.DownstreamScope;
                }
                return scope;
            }

            private SqlSelect InvertSelect(Scope innerScope, SqlDuplicator dup, bool clearSelect,
                                           Expression sourceExpression)
            {
                innerScope.Select.OrderingType = SqlOrderingType.Always;
                SqlSelect select3 = (SqlSelect) dup.Duplicate(innerScope.Select);
                if ((innerScope.ContainingScope == null) || (innerScope.ContainingScope.Select == null))
                {
                    return select3;
                }
                Scope scope2 = GetFurthestDownstreamScope(innerScope.ContainingScope);
                if (scope2.Select == null)
                {
                    return select3;
                }
                scope2.Select.OrderingType = SqlOrderingType.Always;
                SqlSelect select4 = this.InvertSelect(scope2, dup, false, sourceExpression);
                if (clearSelect && !IsTableAlias(select4.From))
                {
                    select4.Selection = new SqlNop(select4.Selection.ClrType, select4.Selection.SqlType,
                                                   select4.SourceExpression);
                    select4.Row.Columns.Clear();
                }
                if (!IsInvertible(select4))
                {
                    SqlAlias alias3 = new SqlAlias(select4);
                    select4 = new SqlSelect(new SqlAliasRef(alias3), alias3, sourceExpression);
                }
                SqlAlias alias2 = new SqlAlias(select3);
                select4.From = new SqlJoin(SqlJoinType.CrossApply, select4.From, alias2, null, sourceExpression);
                select4.Selection = new SqlAliasRef(alias2);
                return select4;
            }

            private static bool IsInvertible(SqlSelect select)
            {
                if (!select.IsDistinct && (select.Top == null))
                {
                    return (select.GroupBy.Count == 0);
                }
                return false;
            }

            private static bool IsTableAlias(SqlSource source)
            {
                SqlAlias alias2 = source as SqlAlias;
                if (alias2 != null)
                {
                    return (alias2.Node is SqlTable);
                }
                return false;
            }

            internal override SqlAlias VisitAlias(SqlAlias a)
            {
                bool flag2 = this.isTopLevel;
                Scope scope2 = this.currentScope;
                this.currentScope = new Scope(this.currentScope.ContainingScope, this.currentScope, null);
                this.isTopLevel = false;
                SqlAlias alias2 = base.VisitAlias(a);
                this.currentScope = scope2;
                this.isTopLevel = flag2;
                return alias2;
            }

            internal override SqlExpression VisitClientCase(SqlClientCase c)
            {
                c.Expression = VisitExpression(c.Expression);
                List<SqlExpression> list3 = new List<SqlExpression>();
                List<SqlExpression> list4 = new List<SqlExpression>();
                bool flag2 = false;
                int num1 = 0;
                int num2 = c.Whens.Count;
                while (num1 < num2)
                {
                    SqlClientWhen when1 = c.Whens[num1];
                    SqlExpression expression1 = VisitExpression(when1.Match);
                    SqlExpression expression2 = VisitExpression(when1.Value);
                    list3.Add(expression1);
                    list4.Add(expression2);
                    flag2 = flag2 || ((expression1 != when1.Match) || (expression2 != when1.Value));
                    num1++;
                }
                if (flag2)
                {
                    return this.sql.Case(c.ClrType, c.Expression, list3, list4, c.SourceExpression);
                }
                return c;
            }

            internal override SqlExpression VisitElement(SqlSubSelect elem)
            {
                if (this.isTopLevel)
                {
                    bool flag1 = this.isTopLevel;
                    this.isTopLevel = false;
                    SqlExpression expression1 = this.ConvertToClientQuery(elem);
                    this.isTopLevel = flag1;
                    return expression1;
                }
                return new SqlNop(elem.ClrType, elem.SqlType, elem.SourceExpression);
            }

            internal override SqlExpression VisitExists(SqlSubSelect ss)
            {
                Scope scope2 = this.currentScope;
                this.currentScope = new Scope(this.currentScope, this.currentScope.DownstreamScope, null);
                SqlExpression expression2 = base.VisitExists(ss);
                this.currentScope = scope2;
                return expression2;
            }

            internal override SqlSource VisitJoin(SqlJoin join)
            {
                Scope scope2 = this.currentScope;
                switch (join.JoinType)
                {
                    case SqlJoinType.CrossApply:
                    case SqlJoinType.OuterApply:
                        this.currentScope = new Scope(this.currentScope.ContainingScope,
                                                      this.currentScope.DownstreamScope, null);
                        Visit(join.Left);
                        this.currentScope = new Scope(this.currentScope, this.currentScope.DownstreamScope, null);
                        Visit(join.Right);
                        this.currentScope = new Scope(this.currentScope, this.currentScope.DownstreamScope, null);
                        Visit(join.Condition);
                        break;

                    default:
                        base.VisitJoin(join);
                        break;
                }
                this.currentScope = scope2;
                return join;
            }

            internal override SqlExpression VisitMultiset(SqlSubSelect sms)
            {
                if (this.isTopLevel)
                {
                    bool flag1 = this.isTopLevel;
                    this.isTopLevel = false;
                    SqlExpression expression1 = this.ConvertToClientQuery(sms);
                    this.isTopLevel = flag1;
                    return expression1;
                }
                return new SqlNop(sms.ClrType, sms.SqlType, sms.SourceExpression);
            }

            internal override SqlQuerySet VisitQuerySet(SqlQuerySet qs)
            {
                this.querySet = qs;
                for (int num1 = 0; num1 < qs.Queries.Count; num1++)
                {
                    this.isTopLevel = true;
                    qs.Queries[num1] = Visit(qs.Queries[num1]);
                }
                return qs;
            }

            internal override SqlExpression VisitScalarSubSelect(SqlSubSelect ss)
            {
                Scope scope2 = this.currentScope;
                this.currentScope = new Scope(this.currentScope, this.currentScope.DownstreamScope, null);
                SqlExpression expression2 = base.VisitScalarSubSelect(ss);
                this.currentScope = scope2;
                return expression2;
            }

            internal override SqlStatement VisitCursor(SqlCursor c)
            {
                c.Select = this.VisitSelect(c.Select);
                return c;
            }

            internal override SqlSelect VisitSelect(SqlSelect select)
            {
                Scope scope2 = this.currentScope;
                this.currentScope = new Scope(this.currentScope.ContainingScope, this.currentScope.DownstreamScope,
                                              select);
                select = base.VisitSelect(select);
                this.currentScope = scope2;
                return select;
            }

            #region Nested type: Scope

            private class Scope
            {
                // Methods

                // Fields
                internal readonly Scope ContainingScope;
                internal readonly Scope DownstreamScope;
                internal readonly SqlSelect Select;

                internal Scope(Scope containing, Scope downstream, SqlSelect select)
                {
                    this.ContainingScope = containing;
                    this.DownstreamScope = downstream;
                    this.Select = select;
                }
            }

            #endregion
        }

        #endregion
    }
}